#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cmath>
#include "realvariablebag.hpp"
#include <stack>
#include <cctype>
#include <algorithm>
#include "BASIC_like_std_string_functions.hpp"

using namespace std;

struct line
{
  unsigned lineNumber;
  string instruction;
};
vector<line> lines;  //This contains the program

realvariablebag rvb; //user variables

struct forStructure
{
  string var;      //counter variable
  double target;   //target value
  double step;     //amount to increment
  unsigned location;
};
stack<forStructure> forStack;  //stack for FOR/NEXT loop

stack<unsigned> gosubStack;    //stack for GOBUB/RETURN

void loadProgram(char *fileName), executeInstructions();
double eval(string expression);

int main(int argc, char *argv[])
{
  if(argc != 2)
  {
    cout << "Error! Bad command line arguments" << endl;
    return EXIT_SUCCESS;
  }
  loadProgram(argv[1]);
  executeInstructions();
  return EXIT_SUCCESS;
}

void loadProgram(char *fileName)
{
  ifstream myfile(fileName);
  string s;
  unsigned number;
  line lineOfCode;
  if(myfile.is_open())
  {
    while(!myfile.eof())
    {
      getline(myfile, s);
      s = TRIM(s);
      if(s.size() == 0)  //blank
        continue;
      number = 0;
      while(!s.empty() && isdigit(s[0]))
      {
        number = number * 10 + (s[0] - '0');
        s.erase(0, 1);
      }
      if(number == 0)
      {
        cout << "Error! Bad line number" << endl;
        exit(EXIT_SUCCESS);
      }
      lineOfCode.instruction = LTRIM(s);
      lineOfCode.lineNumber = number;
      lines.push_back(lineOfCode);
    }
  }
  else
  {
    cout << "Unable to open file" << endl;
    exit(EXIT_SUCCESS);
  }
}

double eval(string expression)
{
  string s1, s2;
  char op;
  double num;
  while(expression.find(' ') != string::npos)
  {
    expression.erase(expression.find(' '), 1);  //remove all spaces
  }
  if(expression.empty())
  {
    cout << "Invalid expression" << endl;
    exit(EXIT_SUCCESS);
  }
  while(!expression.empty() && expression[0] != '+' && expression[0] != '-' && expression[0] != '*' && expression[0] != '/' && expression[0] != '^')
  {
    s1.push_back(expression[0]);
    expression.erase(0, 1);
  }
  if(expression.empty())
  {
    if(s1.empty())
    {
      cout << "Invalid expression" << endl;
      exit(EXIT_SUCCESS);
    }
    return (isdigit(s1[0]) ? atof(s1.c_str()) : rvb.get(s1));
  }
  num = (isdigit(s1[0]) ? atof(s1.c_str()) : rvb.get(s1));
  reiteratepoint:
  s2.clear();
  if(expression[0] == '+' || expression[0] == '-' || expression[0] == '*' || expression[0] == '/' || expression[0] == '^')
  {
    op = expression[0];
    expression.erase(0, 1);
    while(!expression.empty() && expression[0] != '+' && expression[0] != '-' && expression[0] != '*' && expression[0] != '/' && expression[0] != '^')
    {
      s2.push_back(expression[0]);
      expression.erase(0, 1);
    }
    if(s2.empty())
    {
      cout << "Invalid expression" << endl;
      exit(EXIT_SUCCESS);
    }
    switch(op)
    {
      case '+':
        num = num + (isdigit(s2[0]) ? atof(s2.c_str()) : rvb.get(s2));
        break;
      case '-':
        num = num - (isdigit(s2[0]) ? atof(s2.c_str()) : rvb.get(s2));
        break;
      case '*':
        num = num * (isdigit(s2[0]) ? atof(s2.c_str()) : rvb.get(s2));
        break;
      case '/':
        num = num / (isdigit(s2[0]) ? atof(s2.c_str()) : rvb.get(s2));
        break;
      case '^':
        num = pow(num, (isdigit(s2[0]) ? atof(s2.c_str()) : rvb.get(s2)));
        break;
    }
    if(expression.empty())
      return num;
    goto reiteratepoint;
  }
  else
  {
    cout << "Invalid expression" << endl;
    exit(EXIT_SUCCESS);
  }
}

void executeInstructions()
{
  unsigned currentLine = 0, currentChar = 0;
  if(lines.size() == 0) // if file is empty then quit
    exit(EXIT_SUCCESS);
  vector<double> dataList;
  unsigned dataPosition = 0;
  //***start of code to extract DATA from lines***
  for(unsigned i = 0; i < lines.size(); i++)
  {
    if(UCASE(lines[i].instruction.substr(0, 4)) == "DATA")
    {
      string numberAsString, s;
      s = lines[i].instruction;
      s.erase(0, 4);
      s = TRIM(s);
      while(true)
      {
        if(s.empty())
        {
          if(numberAsString.size() > 0)
          {
            dataList.push_back(atof(numberAsString.c_str()));
            numberAsString.clear();
            break;
          }
          else
          {
            cout << "Error in line " << lines[i].lineNumber << endl;
            exit(EXIT_SUCCESS);
          }
        }
        if(isdigit(s[0]) || s[0] == '.')
        {
          numberAsString.push_back(s[0]);
          s.erase(0, 1);
        }
        else if(s[0] == ',')
        {
          s.erase(0, 1);
          s = LTRIM(s);
          if(numberAsString.size() > 0)
          {
            dataList.push_back(atof(numberAsString.c_str()));
            numberAsString.clear();
          }
          else
          {
            cout << "Error in line " << lines[i].lineNumber << endl;
            exit(EXIT_SUCCESS);
          }
        }
        else
        {
          cout << "Error in line " << lines[i].lineNumber << endl;
          exit(EXIT_SUCCESS);
        }
      }
    }
  }
  //***end of code to extract DATA from lines***
  while(currentLine < lines.size())
  {
    start:
    if(UCASE(lines[currentLine].instruction.substr(0, 3)) == "REM" || UCASE(lines[currentLine].instruction.substr(0, 4)) == "DATA")
    {
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction) == "CLS")
    {
      system("CLS");
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction) == "END")
    {
      exit(EXIT_SUCCESS);
    }
    if(UCASE(lines[currentLine].instruction) == "BEEP")
    {
      cout << '\a';
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction.substr(0, 4)) == "GOTO")
    {
      string s = lines[currentLine].instruction;
      unsigned number;
      s.erase(0, 4);
      s = TRIM(s);
      if(s.empty() || !isalnum(s[0]))
      {
        cout << "Error in line " << lines[currentLine].lineNumber << endl;
        exit(EXIT_SUCCESS);
      }
      number = unsigned(eval(s));
      for(unsigned i = 0; i < lines.size(); i++)
      {
        if(lines[i].lineNumber == number)
        {
          currentLine = i;
          goto start;
        }
      }
      cout << "Error in line " << lines[currentLine].lineNumber << " you tried to GOTO " << number <<" but that line doesn't exist" << endl;
      exit(EXIT_SUCCESS);
    }
    if(UCASE(lines[currentLine].instruction.substr(0, 3)) == "LET")
    {
      string s = lines[currentLine].instruction, name;
      s.erase(0, 3);
      s = TRIM(s);
      while(!s.empty() && s[0] != ' ' && s[0] != '=')
      {
        name.push_back(s[0]);
        s.erase(0, 1);
      }
      s = LTRIM(s);
      if(s.empty() || s[0] != '=')
      {
        cout << "Error in line " << lines[currentLine].lineNumber << endl;
        exit(EXIT_SUCCESS);
      }
      s.erase(0, 1);
      rvb.set(name, eval(s));
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction.substr(0, 5)) == "PRINT")
    {
      char lastCh;
      string s = lines[currentLine].instruction, s2;
      s.erase(0, 5);
      while(!s.empty())
      {
        s = LTRIM(s);
        if(s[0] == '\"')
        {
          s.erase(0, 1);
          while(!s.empty() && s[0] != '\"')
          {
            cout << s[0];
            s.erase(0, 1);
          }
          if(s.empty()) //no closeing quote
          {
            cout << "Error in line " << lines[currentLine].lineNumber << " no closeing quote" << endl;
            exit(EXIT_SUCCESS);
          }
          lastCh = '\"';
          s.erase(0, 1);
        }
        else
        {
          s2.clear();
          while(!s.empty() && s[0] != ';')
          {
            s2.push_back(s[0]);
            lastCh = s[0];
            s.erase(0, 1);
          }
          cout << eval(s2);
        }
        s = LTRIM(s);
        if(s.empty()) break;
        if(s[0] == ';')
        {
          lastCh = ';';
          s.erase(0, 1);
        }
        s = LTRIM(s);
      }
      if(lastCh != ';') cout << endl;
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction.substr(0, 5)) == "INPUT")
    {
      string s = lines[currentLine].instruction, s2;
      s.erase(0, 5);
      s = LTRIM(s);
      if(s.empty())
      {
        cout << "Error in line " << lines[currentLine].lineNumber << endl;
        exit(EXIT_SUCCESS);
      }
      if(s[0] == '\"')
      {
        s.erase(0, 1);
        while(!s.empty() && s[0] != '\"')
        {
          cout << s[0];
          s.erase(0, 1);
        }
        if(s.empty()) //no closeing quote
        {
          cout << "Error in line " << lines[currentLine].lineNumber << endl;
          exit(EXIT_SUCCESS);
        }
        s.erase(0, 1);
        s = LTRIM(s);
        if(s.empty() || s[0] != ';')
        {
          cout << "Error in line " << lines[currentLine].lineNumber << " ; expected" << endl;
          exit(EXIT_SUCCESS);
        }
        s.erase(0, 1);
        s = LTRIM(s);
        if(s.empty() || !isalpha(s[0]))
        {
          cout << "Error in line " << lines[currentLine].lineNumber << endl;
          exit(EXIT_SUCCESS);
        }
        getline(cin, s2);
        rvb.set(s, atof(s2.c_str()));
      }
      else
      {
        if(s.empty() || !isalpha(s[0]))
        {
          cout << "Error in line " << lines[currentLine].lineNumber << endl;
          exit(EXIT_SUCCESS);
        }
        getline(cin, s2);
        rvb.set(s, atof(s2.c_str()));
      }
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction.substr(0, 4)) == "READ")
    {
      string s = lines[currentLine].instruction, name;
      s.erase(0, 4);
      s = LTRIM(s);
      while(true)
      {
        if(s.empty())
        {
          if(name.empty())
          {
            cout << "Error in line " << lines[currentLine].lineNumber << endl;
            exit(EXIT_SUCCESS);
          }
          else
          {
            if(dataList.size() > dataPosition)
            {
              rvb.set(name, dataList[dataPosition]);
              ++dataPosition;
              break;
            }
            cout << "Error in line " << lines[currentLine].lineNumber << " out of DATA" << endl;
            exit(EXIT_SUCCESS);
          }
        }
        if((name.empty() && isalpha(s[0])) || (!name.empty() && (isalnum(s[0]) || s[0] == '_')))
        {
          name.push_back(s[0]);
          s.erase(0, 1);
        }
        else if(s[0] == ',')
        {
          s.erase(0, 1);
          s = LTRIM(s);
          if(name.empty())
          {
            cout << "Error in line " << lines[currentLine].lineNumber << endl;
            exit(EXIT_SUCCESS);
          }
          else
          {
            if(dataList.size() > dataPosition)
            {
              rvb.set(name, dataList[dataPosition]);
              ++dataPosition;
              name.clear();
            }
            else
            {
              cout << "Error in line " << lines[currentLine].lineNumber << " out of DATA" << endl;
              exit(EXIT_SUCCESS);
            }
          }
        }
        else
        {
          cout << "Error in line " << lines[currentLine].lineNumber << endl;
          exit(EXIT_SUCCESS);
        }
      }
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction) == "RESTORE")
    {
      dataPosition = 0;
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction.substr(0, 5)) == "GOSUB")
    {
      string s = lines[currentLine].instruction;
      unsigned number;
      s.erase(0, 5);
      s = LTRIM(s);
      if(s.empty() || !isalnum(s[0]))
      {
        cout << "Error in line " << lines[currentLine].lineNumber << endl;
        exit(EXIT_SUCCESS);
      }
      number = unsigned(eval(s));
      for(unsigned i = 0; i < lines.size(); i++)
      {
        if(lines[i].lineNumber == number)
        {
          gosubStack.push(currentLine);
          currentLine = i;
          goto start;
        }
      }
      cout << "Error in line " << lines[currentLine].lineNumber << " you tried to GOSUB a line that doesn't exist" << endl;
      exit(EXIT_SUCCESS);
    }
    if(UCASE(lines[currentLine].instruction) == "RETURN")
    {
      if(gosubStack.empty())
      {
        cout << "Error in line " << lines[currentLine].lineNumber << " RETURN without GOSUB" << endl;
        exit(EXIT_SUCCESS);
      }
      currentLine = gosubStack.top();
      gosubStack.pop();
      ++currentLine;
      continue;
    }
    if(UCASE(lines[currentLine].instruction.substr(0, 2)) == "IF")
    {
      string s = lines[currentLine].instruction, s2, op;
      unsigned location;
      double left, right;
      s.erase(0, 2);
      while(!s.empty() && s[0] != '<' && s[0] != '>' && s[0] != '=')
      {
        s2.push_back(s[0]);
        s.erase(0, 1);
      }
      if(s.empty())
      {
        cout << "Error in line " << lines[currentLine].lineNumber << endl;
        exit(EXIT_SUCCESS);
      }
      if(s.substr(0, 2) == "<=" || s.substr(0, 2) == ">=" || s.substr(0, 2) == "<>")
      {
        op = s.substr(0, 2);
        s.erase(0, 2);
      }
      else
      {
        op = s.substr(0, 1);
        s.erase(0, 1);
      }
      left = eval(s2);
      s2.clear();
      while(!s.empty() && UCASE(s.substr(0, 4)) != "THEN")
      {
        s2.push_back(s[0]);
        s.erase(0, 1);
      }
      if(s.empty())
      {
        cout << "Error in line " << lines[currentLine].lineNumber << " THEN expected" << endl;
        exit(EXIT_SUCCESS);
      }
      right = eval(s2);
      s.erase(0, 4);
      if((op == "<" && left < right) || (op == ">" && left > right) || (op == "=" && left == right) || (op == "<=" && left <= right) || (op == ">=" && left >= right) || (op == "<>" && left != right))
      {
        location = unsigned(eval(s));
        for(unsigned i = 0; i < lines.size(); i++)
        {
          if(lines[i].lineNumber == location)
          {
            currentLine = i;
            goto start;
          }
        }
        cout << "Error in line " << lines[currentLine].lineNumber << endl;
        exit(EXIT_SUCCESS);
      }
      ++currentLine;
      continue;
    }
    cout << "ERREOR" << endl;
    exit(EXIT_SUCCESS);
  }
}
